#!/usr/bin/python
# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
This contains classes that are used to build the main interface

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


import os
import sys

# GTK+ imports
import pygtk
if not sys.platform == 'win32':
    pygtk.require('2.0')
try:
    import gtk
    import goocanvas
except:
    log.error("gtk or gtk.glade module not found!")
    sys.exit(1)

# Project imports
from log import log
log.debug("Loaded...")

class PanController(object):
    '''This class defines a pan function of the click-and-drag type. It allows
       the definition of reference surface, that must send the mouse events and
       two Adjustments (vertical and horizontal), that will be set according to
       the drag done in the reference surface. It is possible to apply scaling
       in the movement, so that a large surface can be controlled by a small
       area (and vice-versa).'''
    
    STATE_OFF = 1
    STATE_ON = 2
       
    def __init__(self, panButton=1):
        
        self.state = self.STATE_OFF
        
        self.canvas = None
        self.vadjust = None
        self.hadjust = None
        
        self.vscale = 1.0
        self.hscale = 1.0
        
        self.xReference = 0.0
        self.yReference = 0.0
        
        self.panButton = panButton
        
        # These will be used to allow disconnection
        self.buttonPressHandleId = None
        self.buttonReleaseHandleId = None
        self.mouseMovementHandleId = None
        
        self.defaultCursor = None
        
    def connect(self,
                canvas=None, vadjust=None, hadjust=None,
                vscale=1.0, hscale=1.0, scrolledWindow = None):
        '''Connects this pan controller to the reference Canvas and to the
           Adjustments vadjust and hadjust. Optionally, instead of a vadjust and
           a hadjust a scrolledWindow may be passed.'''

        if scrolledWindow != None:
            if vadjust != None or hadjust != None:
                raise Exception('If scrolledWindow is set, vadjust and ' +
                                'hadjust should not be set')
            else:
                vadjust = scrolledWindow.get_vadjustment()
                hadjust = scrolledWindow.get_hadjustment()

        self.canvas = canvas
        self.vadjust = vadjust
        self.hadjust = hadjust
        self.vscale = vscale
        self.hscale = hscale
        
        self.enterOff()
        
    def disconnect(self):
        '''Completely disconnects this controller from all event handlers.'''
        
        # Simply disconnects all the event handlers
        if self.buttonPressHandleId != None:
            self.canvas.disconnect(self.buttonPressHandleId)
            self.buttonPressHandleId = None
        if self.buttonReleaseHandleId != None:
            self.canvas.disconnect(self.buttonReleaseHandleId)
            self.buttonReleaseHandleId = None
        if self.mouseMovementHandleId != None:
            self.canvas.disconnect(self.mouseMovementHandleId)
            self.mouseMovementHandleId = None
    
    # The following methods set up the state
    def enterOff(self):
        '''Sets up the OFF state.'''
        
        self.disconnect()
        self.buttonPressHandleId = self.canvas.connect('button-press-event',
                                                       self.handleButtonPress)
        self.state = self.STATE_OFF
    
    def enterOn(self, x, y):
        '''Sets up the ON state '''
        
        if self.state != self.STATE_ON:
            
            self.disconnect()
            
            self.xReference = x
            self.yReference = y
            
            # Saves the current cursor type and sets the new one
            window = self.canvas.get_window()
            self.defaultCursor = window.get_cursor()
            
            cursor = gtk.gdk.Cursor(gtk.gdk.FLEUR)
            window.set_cursor(cursor)
            
            # Now sets the new event handlers
            self.buttonReleaseHandleId = self.canvas.connect('button-release-event',
                                                             self.handleButtonRelease)
            self.mouseMovementHandleId = self.canvas.connect('motion_notify_event',
                                                             self.handleMouseMovement)
            
            self.state = self.STATE_ON
    
    def leaveOn(self):
        if self.state == self.STATE_ON:
            self.disconnect()
            
            # Sets the current cursor type as the old one
            window = self.canvas.get_window()
            window.set_cursor(self.defaultCursor)
    
    # The following methods handle events. They just check the event and call
    #  the correct state setting method
    def handleButtonPress(self, widget, event):
        message = ('\n    Source: %s\n    Event: %s' % (repr(widget), repr(event)))
        log.info(message)
       
        if event.button == self.panButton and self.state == self.STATE_OFF:
            self.enterOn(event.x, event.y)

    def handleButtonRelease(self, widget, event):
        message = ('\n    Source: %s\n    Event: %s' % (repr(widget), repr(event)))
        log.info(message)
        
        if event.button == self.panButton and self.state == self.STATE_ON:
            self.leaveOn()
            self.enterOff()

    def handleMouseMovement(self, widget, event):
        message = ('\n    Source: %s\n    Event: %s' % (repr(widget), repr(event)))
        log.info(message)
        
        if self.state == self.STATE_ON:
            
            x = event.x
            y = event.y
            
            deltaX = (x - self.xReference)*self.hscale
            deltaY = (y - self.yReference)*self.vscale
            
            if abs(deltaX) > 0:
                self.incrementAdjustment(self.hadjust, -deltaX)
            if abs(deltaY) > 0:
                self.incrementAdjustment(self.vadjust, -deltaY)
            
    # The following functions are ancillary only
    def incrementAdjustment(self, adjust, increment):
        '''Simply sets the adjustment to a new value, adding 'increment' to it'''
        
        value = adjust.get_value()
        adjust.set_value(value + increment)
            

class Application(object):
    """Standard application interface. Just a menu to open the c-maps
    """
   
    # This is a callback function. The data arguments are ignored
    # in this example. More on callbacks below.
    def hello(self, widget, data=None):
        log.info("Hello World")

    def delete_event(self, widget, event, data=None):
        # If you return FALSE in the "delete_event" signal handler,
        # GTK will emit the "destroy" signal. Returning TRUE means
        # you don't want the window to be destroyed.
        # This is useful for popping up 'are you sure you want to quit?'
        # type dialogs.
        log.info("delete event occurred")

        # Change FALSE to TRUE and the main window will not be destroyed
        # with a "delete_event".
        return False

    def destroy(self, widget, data=None):
        log.info("destroy signal occurred")
        gtk.main_quit()
        
    def logEvent(self, widget, tp):
      'Prints a function to log message'
      
      def logEventInternal(source, event):
        message = '%s %s\n    ' % (widget, tp)
        message += ('Source: %s\n    Event: %s' % (repr(source), repr(event)))
      
        log.info(message)
        
        return False  # Continue processing the event
      
      return logEventInternal
    
    def connectScrollEvent(self, source, adj):
        'Connects scroll events from source to the adjustment adj'
        
        def connectScrollEventCallback(widget, event):
            message = 'connectScrollEvent\n    '
            message += ('Source: %s\n    Event: %s' % (repr(source), repr(event)))
            log.debug(message)
            
            value = adj.get_value()
            step = adj.get_step_increment()
            if event.direction == gtk.gdk.SCROLL_UP:
                adj.set_value(value - step)
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                adj.set_value(value + step)
            else:
                log.info('Event direction: %s' % repr(event.direction))
            
            return False  # keeps processing events
            
        source.connect('scroll_event', connectScrollEventCallback)
            
        
    def playGoo(self):
        '''Play a little bit with the canvas '''
      
#        self.goo.connect('button-press-event', self.logEvent('Canvas', 'button press'))
#        self.goo.connect('button-release-event', self.logEvent('Canvas', 'button release'))
#        self.goo.connect('motion_notify_event', self.logEvent('Canvas', 'Motion notify'))
#        self.goo.connect('scroll_event', self.logEvent('Canvas', 'scroll'))
        
        pb = gtk.gdk.pixbuf_new_from_file(r'/home/rrs/07 - Imagens/snakes.jpg')
        
        log.info('Bounds = (%d, %d, %d, %d)' % self.goo.get_bounds())
        
        cont_left, cont_top, cont_right, cont_bottom = self.goo.get_bounds()
        img_w = pb.get_width()
        img_h = pb.get_height()
        img_left = (cont_right - img_w)/2
        img_top = (cont_bottom - img_h)/2
        
        goocanvas.Image(pixbuf=pb,
                      parent=self.root,
                      x=img_left,
                      y=img_top) 
        
        grid = goocanvas.Grid(parent=self.root,
                              x=100,
                              y=100,
                              width=400,
                              height=200,
                              x_step=20,
                              y_step=20,
                              x_offset=10,
                              y_offset=10,
                              horz_grid_line_width=4.0,
                              horz_grid_line_color="yellow",
                              vert_grid_line_width=2.0,
                              vert_grid_line_color="red",
                              border_width=3.0,
                              border_color="white",
                              fill_color="blue"
                             )
      

    def __init__(self):
        # create a new window    
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    
        # When the window is given the "delete_event" signal (this is given
        # by the window manager, usually by the "close" option, or on the
        # titlebar), we ask it to call the delete_event () function
        # as defined above. The data passed to the callback
        # function is NULL and is ignored in the callback function.
        self.window.connect("delete_event", self.delete_event)
    
        # Here we connect the "destroy" event to a signal handler.  
        # This event occurs when we call gtk_widget_destroy() on the window,
        # or if we return FALSE in the "delete_event" callback.
        self.window.connect("destroy", self.destroy)
    
        # Sets the border width of the window.
        self.window.set_border_width(10)
    
        # Creates a new button with the label "Hello World".
        self.button = gtk.Button("Hello World")
    
        # When the button receives the "clicked" signal, it will call the
        # function hello() passing it None as its argument.  The hello()
        # function is defined above.
        self.button.connect("clicked", self.hello, None)
    
        # This will cause the window to be destroyed by calling
        # gtk_widget_destroy(window) when "clicked".  Again, the destroy
        # signal could come from here, or the window manager.
        self.button.connect_object("clicked", gtk.Widget.destroy, self.window)
        
        self.button2 = gtk.Button("Last button")
        
        # Play with gooCanvas and scrooled windows
        self.goo = goocanvas.Canvas()
        self.goo.set_size_request(1000, 1000)
        self.goo.set_bounds(0, 0, 1000, 1000)
        self.root = self.goo.get_root_item()
        self.playGoo()
        
        self.scroll = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
        self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.scroll.set_size_request(300, 300)
        self.scroll.add_with_viewport(self.goo)
        
        self.connectScrollEvent(self.goo, self.scroll.get_vadjustment())
        
        pc = PanController()
        pc.connect(canvas=self.goo,
                   scrolledWindow = self.scroll)
        
        
        self.box = gtk.VBox()
        self.box.set_property("spacing", 6)
        self.box.add(self.button)
        self.box.add(self.scroll)
        self.box.add(self.button2)
        
        # This packs the button into the window (a GTK container).
        self.window.add(self.box)
    
        # and the window
        self.window.show_all()

    def main(self):
        # All PyGTK applications must have a gtk.main(). Control ends here
        # and waits for an event to occur (like a key press or mouse event).
        gtk.main()

# If the program is run directly or passed as an argument to the python
# interpreter then create a HelloWorld instance and show it
if __name__ == "__main__":
    app = Application()
    app.main()

